package com.azhe.myframework.servlet.v1;

import com.azhe.myframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * Description:
 *
 * @author Linzr
 * @version V1.0.0
 * @date 2021/1/8 11:18 上午
 * @since V1.0.0
 */
public class MyDispatcherServlet extends HttpServlet {
    // 接收配置文件application.properties
    Properties properties = new Properties();

    // 保存需要实例化并装进ioc的类名
    private final List<String> fileNameList = new ArrayList<String>();

    // ioc容器
    private final Map<String, Object> ioc = new HashMap<String, Object>();

    // 保存url和具体方法的映射
    private final Map<String, Method> handlerMapping = new HashMap<String, Method>();

    /**
     * 初始化 模板方法模式
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        // 加载配置
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        // 扫描包路径下的所有class类
        doScanner(properties.getProperty("scanPackage"));
        // 初始化bean装载ioc
        initIoc();
        // 自动依赖注入
        doAutowired();
        // 初始化HandlerMapping
        initHandlerMapping();
    }
    private void doLoadConfig(String config) {
        try {
            // 将配置文件转成输入流并对接上Properties属性
            InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(config);
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void doScanner(String basePath) {
        URL baseResource = this.getClass().getClassLoader().getResource(basePath.replaceAll("\\.", "/"));
        // 获得basePath对应的文件/文件夹
        File baseFile = new File(baseResource.getFile());
        // 遍历basePath下的所有文件
        for (File file : baseFile.listFiles()) {
            if (file.isDirectory()) {
                doScanner(basePath + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                fileNameList.add(basePath + "." + file.getName().replace(".class", ""));
            }
        }
    }

    private void initIoc() {
        try {
            if (fileNameList.isEmpty()) { return;}
            for (String fileName : fileNameList) {
                // 获得bean类信息
                Class<?> clazz = Class.forName(fileName);
                instanceFile(clazz);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doAutowired() {
        try {
            // 遍历ioc容器内的所有bean
            for (Object bean : ioc.values()) {
                // 遍历bean下的所有属性
                for (Field field : bean.getClass().getDeclaredFields()) {
                    if (!field.isAnnotationPresent(MyAutowired.class)) {continue;}

                    MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                    String beanName = !"".equals(annotation.value())? annotation.value(): toLowFirstChar(field.getType().getSimpleName());
                    field.setAccessible(true);
                    field.set(bean, ioc.get(beanName));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initHandlerMapping() {
        for (Object bean : ioc.values()) {
            if (!bean.getClass().isAnnotationPresent(MyController.class)) {continue;}
            String baseUrl = "/";
            if (bean.getClass().isAnnotationPresent(MyRequestMapping.class)) {
                baseUrl += bean.getClass().getAnnotation(MyRequestMapping.class).value();
            }
            Method[] methods = bean.getClass().getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)){continue;}
                MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                String url = (baseUrl + "/" + annotation.value()).replaceAll("/+", "/");
                handlerMapping.put(url, method);
                System.out.println(url + ":" + method);
            }
        }
        System.out.println("handlerMapping had init finish");
    }


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatcher(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }


    /**
     * 处理请求
     * @param req
     * @param resp
     */
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
        try {
            // 获得请求的路径
            String url = req.getRequestURI().replaceAll(req.getContextPath(), "").replaceAll("/+", "/");
            if (!handlerMapping.containsKey(url)) {
                resp.getWriter().write("404 NOT FOUND!");
                return;
            }
            // 匹配对应的方法，利用反射运行
            Method method = handlerMapping.get(url);
            // 形参
            Class<?>[] parameterTypes = method.getParameterTypes();
            // 实参
            Object[] params = new Object[parameterTypes.length];
            // 请求参数
            Map<String, String[]> parameterMap = req.getParameterMap();
            // 填充实参
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterTypes.length; i++) {
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof MyRequestParam) {
                        String paramName = ((MyRequestParam) annotation).value();
                        params[i] = coverParam(parameterTypes[i], parameterMap.get(paramName));
                    }
                }
            }
            // 反射调用方法
            Object invoke = method.invoke(ioc.get(toLowFirstChar(method.getDeclaringClass().getSimpleName())), params);
            resp.getWriter().write(invoke.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 实例化bean
     * @param clazz
     */
    private void instanceFile(Class<?> clazz) throws Exception {
        // MyController 注解
        if (clazz.isAnnotationPresent(MyController.class)) {
            ioc.put(toLowFirstChar(clazz.getSimpleName()), clazz.newInstance());
            return;
        }
        // MyService 注解
        if (clazz.isAnnotationPresent(MyService.class)) {
            Object bean = clazz.newInstance();
            MyService annotation = clazz.getAnnotation(MyService.class);
            String beanName = annotation.value();
            if ("".equals(beanName.trim())) {
                beanName = toLowFirstChar(clazz.getSimpleName());
            }
            ioc.put(beanName, bean);

            // 将接口类型也装备进ioc
            Class<?>[] interfaces = clazz.getInterfaces();
            for (Class<?> anInterface : interfaces) {
                if (ioc.containsKey(anInterface.getSimpleName())) {
                    continue;
                }
                ioc.put(toLowFirstChar(anInterface.getSimpleName()), bean);
            }
        }
    }

    private String toLowFirstChar(String content) {
        char[] chars = content.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    // 将请求数据转换为对应方法需要的类型
    private Object coverParam(Class<?> parameterType, String[] content) {
        if (parameterType == Integer.class) {
            int result = 0;
            for (String num : content) {
                result += Integer.parseInt(num);
            }
            return result;
        }
        if (parameterType == String.class) {
            return Arrays.toString(content).replaceAll("[\\[\\]]", "").replaceAll("s+", ",");
        }
        return null;
    }

}
